Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Displaying Raw • Download


meshchatx/src/backend/http_url_guard.py a9cc4b996b8da8ff414e305825f372ca12dc138d (a9cc4b99) Text, 4.90 KB

T8b949e# SPDX-License-Identifier: 0BSD

T8b949e"""Shared HTTP(S) URL validation for outbound client requests (e.g. LibreTranslate)."""

Tff7b72from T7ee787__future__ Tff7b72import Te6edf3annotations

Tff7b72import T7ee787ipaddress
Tff7b72import T7ee787re
Tff7b72from T7ee787urllibT7ee787.T7ee787parse Tff7b72import Te6edf3unquoteTb4b4b4, Te6edf3urlparseTb4b4b4, Te6edf3urlunparse


Tff7b72class T56d364UnsafeOutboundUrlErrorTb4b4b4(Tf85149ValueErrorTb4b4b4)Tb4b4b4:
T8b949e"""Raised when a URL is not permitted for server-side fetch."""


Tff7b72def Td2a8ffnormalize_loopback_http_service_baseTb4b4b4(Te6edf3urlTb4b4b4: Tffa657strTb4b4b4) Tff7b72-Tff7b72> Tffa657strTb4b4b4:
T8b949e"""Return scheme://host:port with no path, query, or fragment.

Only http/https to loopback hosts (127.0.0.1, localhost, ::1) are allowed.
Userinfo (embedded credentials) is rejected.
"""
Tff7b72if Tff7b72not Te6edf3url Tff7b72or Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3urlTb4b4b4, Tffa657strTb4b4b4)Tb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffURL must be a non-empty stringTa5d6ff"
Tff7b72raise Te6edf3UnsafeOutboundUrlErrorTb4b4b4(Te6edf3msgTb4b4b4)

Te6edf3parsed Tff7b72= Te6edf3urlparseTb4b4b4(Te6edf3urlTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72if Te6edf3parsedTff7b72.Td2a8ffscheme Tff7b72not Tff7b72in Tb4b4b4(Ta5d6ff"Ta5d6ffhttpTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffhttpsTa5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffURL must use http or httpsTa5d6ff"
Tff7b72raise Te6edf3UnsafeOutboundUrlErrorTb4b4b4(Te6edf3msgTb4b4b4)

Te6edf3netloc Tff7b72= Te6edf3parsedTff7b72.Td2a8ffnetloc Tff7b72or Ta5d6ff"Ta5d6ff"
Tff7b72if Ta5d6ff"Ta5d6ff@Ta5d6ff" Tff7b72in Te6edf3netlocTb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffURL must not contain credentialsTa5d6ff"
Tff7b72raise Te6edf3UnsafeOutboundUrlErrorTb4b4b4(Te6edf3msgTb4b4b4)

Te6edf3host Tff7b72= Te6edf3parsedTff7b72.Td2a8ffhostname
Tff7b72if Te6edf3host Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffURL must include a hostnameTa5d6ff"
Tff7b72raise Te6edf3UnsafeOutboundUrlErrorTb4b4b4(Te6edf3msgTb4b4b4)

Te6edf3host_norm Tff7b72= Te6edf3hostTff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Ta5d6ff"Ta5d6ff[]Ta5d6ff"Tb4b4b4)
Tff7b72if Te6edf3host_norm Tff7b72not Tff7b72in Tb4b4b4(Ta5d6ff"Ta5d6ff127.0.0.1Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6fflocalhostTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff::1Ta5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffURL host must be 127.0.0.1, localhost, or ::1Ta5d6ff"
Tff7b72raise Te6edf3UnsafeOutboundUrlErrorTb4b4b4(Te6edf3msgTb4b4b4)

Te6edf3authority Tff7b72= Te6edf3netloc
Te6edf3origin Tff7b72= Te6edf3urlunparseTb4b4b4(Tb4b4b4(Te6edf3parsedTff7b72.Td2a8ffschemeTb4b4b4, Te6edf3authorityTb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4)Tb4b4b4)
Tff7b72return Te6edf3originTff7b72.Td2a8ffrstripTb4b4b4(Ta5d6ff"Ta5d6ff/Ta5d6ff"Tb4b4b4)


Te6edf3_WS_CTRL Tff7b72= Te6edf3reTff7b72.Td2a8ffcompileTb4b4b4(Ta5d6ffrTa5d6ff"Ta5d6ff[Ta5d6ff\Ta5d6ffx00-Ta5d6ff\Ta5d6ffx20Ta5d6ff\Ta5d6ffx7f]Ta5d6ff"Tb4b4b4)


Tff7b72def Td2a8ff_coerce_host_to_ipTb4b4b4(
Te6edf3hostTb4b4b4: Tffa657strTb4b4b4,
Tb4b4b4) Tff7b72-Tff7b72> Te6edf3ipaddressTff7b72.Td2a8ffIPv4Address Tff7b72| Te6edf3ipaddressTff7b72.Td2a8ffIPv6Address Tff7b72| Tff7b72NoneTb4b4b4:
T8b949e"""Parse dotted/colon IPs plus decimal and 0x hex IPv4 host forms browsers accept."""
Tff7b72if Tff7b72not Te6edf3hostTb4b4b4:
Tff7b72return Tff7b72None
Tff7b72tryTb4b4b4:
Tff7b72return Te6edf3ipaddressTff7b72.Td2a8ffip_addressTb4b4b4(Te6edf3hostTb4b4b4)
Tff7b72except Tf85149ValueErrorTb4b4b4:
Tff7b72pass
T8b949e# Integer / hex IPv4 (e.g. 2852039166, 0xa9fea9fe -> 169.254.169.254).
Tff7b72tryTb4b4b4:
Tff7b72if Te6edf3hostTff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ff"Ta5d6ff0xTa5d6ff"Tb4b4b4) Tff7b72or Te6edf3hostTff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ff"Ta5d6ff0XTa5d6ff"Tb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3ipaddressTff7b72.Td2a8ffIPv4AddressTb4b4b4(Tffa657intTb4b4b4(Te6edf3hostTb4b4b4, T79c0ff16Tb4b4b4)Tb4b4b4)
Tff7b72if Te6edf3hostTff7b72.Td2a8ffisdigitTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3ipaddressTff7b72.Td2a8ffIPv4AddressTb4b4b4(Tffa657intTb4b4b4(Te6edf3hostTb4b4b4)Tb4b4b4)
Tff7b72except Tb4b4b4(Tf85149ValueErrorTb4b4b4, Tf85149OverflowErrorTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72None
Tff7b72return Tff7b72None


Tff7b72def Td2a8ffnormalize_libretranslate_http_service_baseTb4b4b4(Te6edf3urlTb4b4b4: Tffa657strTb4b4b4) Tff7b72-Tff7b72> Tffa657strTb4b4b4:
T8b949e"""Return scheme://host:port with no path, query, or fragment.

Accepts any HTTP(S) hostname or IP reachable from this process (remote LibreTranslate or
public API). Embedded credentials are rejected; non-http(s) schemes are rejected.

Literal IPv4 link-local targets (169.254.0.0/16) are rejected as a common SSRF/metadata
path, including decimal and hex encodings of those addresses. Other private or loopback
addresses are allowed so local servers and overlays (e.g. VPN mesh) continue to work.
"""
Tff7b72if Tff7b72not Te6edf3url Tff7b72or Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3urlTb4b4b4, Tffa657strTb4b4b4)Tb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffURL must be a non-empty stringTa5d6ff"
Tff7b72raise Te6edf3UnsafeOutboundUrlErrorTb4b4b4(Te6edf3msgTb4b4b4)

Te6edf3trimmed Tff7b72= Te6edf3urlTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3_WS_CTRLTff7b72.Td2a8ffsearchTb4b4b4(Te6edf3trimmedTb4b4b4)Tb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffURL must not contain whitespace or control charactersTa5d6ff"
Tff7b72raise Te6edf3UnsafeOutboundUrlErrorTb4b4b4(Te6edf3msgTb4b4b4)

Te6edf3parsed Tff7b72= Te6edf3urlparseTb4b4b4(Te6edf3trimmedTb4b4b4)
Tff7b72if Te6edf3parsedTff7b72.Td2a8ffscheme Tff7b72not Tff7b72in Tb4b4b4(Ta5d6ff"Ta5d6ffhttpTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffhttpsTa5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffURL must use http or httpsTa5d6ff"
Tff7b72raise Te6edf3UnsafeOutboundUrlErrorTb4b4b4(Te6edf3msgTb4b4b4)

Te6edf3netloc Tff7b72= Te6edf3parsedTff7b72.Td2a8ffnetloc Tff7b72or Ta5d6ff"Ta5d6ff"
Tff7b72if Ta5d6ff"Ta5d6ff@Ta5d6ff" Tff7b72in Te6edf3netlocTb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffURL must not contain credentialsTa5d6ff"
Tff7b72raise Te6edf3UnsafeOutboundUrlErrorTb4b4b4(Te6edf3msgTb4b4b4)

Te6edf3host Tff7b72= Te6edf3parsedTff7b72.Td2a8ffhostname
Tff7b72if Te6edf3host Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffURL must include a hostnameTa5d6ff"
Tff7b72raise Te6edf3UnsafeOutboundUrlErrorTb4b4b4(Te6edf3msgTb4b4b4)

Te6edf3host_decoded Tff7b72= Te6edf3unquoteTb4b4b4(Te6edf3hostTb4b4b4, Te6edf3errorsTff7b72=Ta5d6ff"Ta5d6ffstrictTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3_WS_CTRLTff7b72.Td2a8ffsearchTb4b4b4(Te6edf3host_decodedTb4b4b4)Tb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffURL must not contain whitespace or control charactersTa5d6ff"
Tff7b72raise Te6edf3UnsafeOutboundUrlErrorTb4b4b4(Te6edf3msgTb4b4b4)

Te6edf3host_for_ip_check Tff7b72= Te6edf3host_decodedTff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Ta5d6ff"Ta5d6ff[]Ta5d6ff"Tb4b4b4)
Te6edf3addr Tff7b72= Te6edf3_coerce_host_to_ipTb4b4b4(Te6edf3host_for_ip_checkTb4b4b4)
Tff7b72if Te6edf3addr Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
T8b949e# Apply policy to the effective IPv4 when the host is IPv4-mapped IPv6
T8b949e# (::ffff:169.254.169.254), and reject native IPv6 link-local (fe80::/10).
Te6edf3effective Tff7b72= Te6edf3addrTff7b72.Td2a8ffipv4_mapped Tff7b72if Tffa657getattrTb4b4b4(Te6edf3addrTb4b4b4, Ta5d6ff"Ta5d6ffipv4_mappedTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4) Tff7b72else Te6edf3addr
Tff7b72if Te6edf3effectiveTff7b72.Td2a8ffis_link_local Tff7b72or Te6edf3addrTff7b72.Td2a8ffis_link_localTb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffURL must not target a link-local addressTa5d6ff"
Tff7b72raise Te6edf3UnsafeOutboundUrlErrorTb4b4b4(Te6edf3msgTb4b4b4)
Tff7b72if Tb4b4b4(
Te6edf3effectiveTff7b72.Td2a8ffis_multicast
Tff7b72or Te6edf3effectiveTff7b72.Td2a8ffis_unspecified
Tff7b72or Te6edf3addrTff7b72.Td2a8ffis_multicast
Tff7b72or Te6edf3addrTff7b72.Td2a8ffis_unspecified
Tb4b4b4)Tb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffURL must not target a multicast or unspecified addressTa5d6ff"
Tff7b72raise Te6edf3UnsafeOutboundUrlErrorTb4b4b4(Te6edf3msgTb4b4b4)
Tff7b72if Te6edf3effectiveTff7b72.Td2a8ffis_reserved Tff7b72or Te6edf3addrTff7b72.Td2a8ffis_reservedTb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffURL must not target a reserved addressTa5d6ff"
Tff7b72raise Te6edf3UnsafeOutboundUrlErrorTb4b4b4(Te6edf3msgTb4b4b4)

Te6edf3authority Tff7b72= Te6edf3netloc
Te6edf3origin Tff7b72= Te6edf3urlunparseTb4b4b4(Tb4b4b4(Te6edf3parsedTff7b72.Td2a8ffschemeTb4b4b4, Te6edf3authorityTb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4)Tb4b4b4)
Tff7b72return Te6edf3originTff7b72.Td2a8ffrstripTb4b4b4(Ta5d6ff"Ta5d6ff/Ta5d6ff"Tb4b4b4)


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────